package com.daxue.configtest.utils;

import com.daxue.configtest.exception.impl.BadArgumentException;
import com.daxue.configtest.others.AlertUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.daxue.configtest.others.AlertUtils.alertTrue;

/**
 * @author daxue0929
 * @date 2023/3/25
 */

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Query {

    private static final String AND = ";";
    private static final String OR = ",";

    private static final String EQUAL = "==";
    private static final String NOT_EQUAL = "!=";

    private static final String IN = "=in=";
    private static final String NOT_IN = "=out=";

    private static final String GREATER = ">";
    private static final String GREATER_EQUAL = ">=";
    private static final String LESS = "<";
    private static final String LESS_EQUAL = "<=";

    private static final String EMPTY_QUERY = "";

    private String compositor;
    private String operand;
    private String query;

    private Query(final String key, final String query) {
        this.compositor = "";
        this.operand = key;
        this.query = query;
    }

    public static Query of(@NonNull final String key) {
        AlertUtils.isTrue(StringUtils.hasText(key), alertTrue("Key not empty"));
        AlertUtils.isFalse(fullQuery(key), alertTrue("Only key"));
        return new Query(key, EMPTY_QUERY);
    }

    public static Query from(final String query) {
        if (!StringUtils.hasText(query)) {
            return emptyQuery();
        }
        AlertUtils.isTrue(fullQuery(query), alertTrue("Full query"));
        return new Query(null, query.trim());
    }

    public static Query from(final Query query) {
        return Objects.isNull(query) ? emptyQuery() : from(query.query);
    }

    public static Query emptyQuery() {
        return new Query(null, EMPTY_QUERY);
    }

    public static boolean fullQuery(String query) {
        return Stream.of(EQUAL, NOT_EQUAL, IN, NOT_IN, GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)
            .anyMatch(query::contains);
    }

    @SuppressWarnings("unchecked")
    public static <T> T escape(@NonNull T value, @NonNull String prefix, @NonNull String postfix) {
        if (value instanceof String) {
            return (T) (prefix + ((String) value).replaceAll("'", "\\\\'") + postfix);
        }
        return value;
    }

    public static <T> Collection<T> escape(@NonNull Collection<T> cl, @NonNull String prefix, @NonNull String postfix) {
        return cl.stream()
            .filter(Objects::nonNull)
            .map(e -> escape(e, prefix, postfix))
            .collect(Collectors.toList());
    }

    public String gen() {
        return query;
    }

    public Query and(@NonNull String key) {
        return comp(AND, key);
    }

    public Query and(@NonNull Query other) {
        return comp(AND, other);
    }

    public Query or(@NonNull String key) {
        return comp(OR, key);
    }

    public Query or(@NonNull Query other) {
        return comp(OR, other);
    }

    public Query nested() {
        query = "(" + query + ")";
        return this;
    }

    public Query isNull() {
        return op(EQUAL, "null", v -> v);
    }

    public Query nonNull() {
        return op(NOT_EQUAL, "null", v -> v);
    }

    public Query eq(String value) {
        return op(EQUAL, value, v -> escape(v, "'", "'"));
    }

    public Query eq(Enum value) {
        return op(EQUAL, value, Enum::name);
    }

    public Query eq(Date value) {
        return op(EQUAL, value, v -> "@" + v.getTime());
    }

    public Query eq(Object value) {
        return op(EQUAL, value, String::valueOf);
    }

    public Query eq(int value) {
        return op(EQUAL, value, v -> "$" + v);
    }

    public Query eq(long value) {
        return op(EQUAL, value, v -> "$" + v + "L");
    }

    public Query eq(double value) {
        return op(EQUAL, value, v -> "$" + v);
    }

    public Query like(String value) {
        return op(EQUAL, value, v -> escape(v, "'*", "*'"));
    }

    public Query like(Object value) {
        return op(EQUAL, value, v -> "'*" + v + "*'");
    }

    public Query notLike(String value) {
        return op(NOT_EQUAL, value, v -> escape(v, "'*", "*'"));
    }

    public Query ne(String value) {
        return op(NOT_EQUAL, value, v -> escape(v, "'", "'"));
    }

    public Query ne(Enum value) {
        return op(NOT_EQUAL, value, Enum::name);
    }

    public Query ne(Date value) {
        return op(NOT_EQUAL, value, v -> "@" + v.getTime());
    }

    public Query ne(int value) {
        return op(NOT_EQUAL, value, v -> "$" + v);
    }

    public Query ne(long value) {
        return op(NOT_EQUAL, value, v -> "$" + v + "L");
    }

    public Query ne(double value) {
        return op(NOT_EQUAL, value, v -> "$" + v);
    }

    public Query gt(Date value) {
        return op(GREATER, value, v -> "@" + v.getTime());
    }

    public Query gt(int value) {
        return op(GREATER, value, v -> "$" + v);
    }

    public Query gt(long value) {
        return op(GREATER, value, v -> "$" + v + "L");
    }

    public Query gt(double value) {
        return op(GREATER, value, v -> "$" + v);
    }

    public Query ge(Date value) {
        return op(GREATER_EQUAL, value, v -> "@" + v.getTime());
    }

    public Query ge(int value) {
        return op(GREATER_EQUAL, value, v -> "$" + v);
    }

    public Query ge(long value) {
        return op(GREATER_EQUAL, value, v -> "$" + v + "L");
    }

    public Query ge(double value) {
        return op(GREATER_EQUAL, value, v -> "$" + v);
    }

    public Query lt(Date value) {
        return op(LESS, value, v -> "@" + v.getTime());
    }

    public Query lt(int value) {
        return op(LESS, value, v -> "$" + v);
    }

    public Query lt(long value) {
        return op(LESS, value, v -> "$" + v + "L");
    }

    public Query lt(double value) {
        return op(LESS, value, v -> "$" + v);
    }

    public Query le(Date value) {
        return op(LESS_EQUAL, value, v -> "@" + v.getTime());
    }

    public Query le(int value) {
        return op(LESS_EQUAL, value, v -> "$" + v);
    }

    public Query le(long value) {
        return op(LESS_EQUAL, value, v -> "$" + v + "L");
    }

    public Query le(double value) {
        return op(LESS_EQUAL, value, v -> "$" + v);
    }

    public <T> Query in(Collection<T> cl) {
        return op(IN, cl, v -> "(" + StringUtils.collectionToCommaDelimitedString(escape(v, "'", "'")) + ")");
    }

    public <T> Query in(T... ar) {
        return in(Arrays.asList(ar));
    }

    public <T> Query notIn(Collection<T> cl) {
        return op(NOT_IN, cl, v -> "(" + StringUtils.collectionToCommaDelimitedString(escape(v, "'", "'")) + ")");
    }

    public <T> Query notIn(T... ar) {
        return notIn(Arrays.asList(ar));
    }

    public <T> Query likeAny(Collection<T> cl) {
        if (!validate(cl)) {
            return clearOp();
        }
        Query tmp = Query.emptyQuery();
        cl.stream().filter(Objects::nonNull).forEach(e -> tmp.or(operand).like(e));
        return comp(compositor, tmp.nested());
    }

    public <T> Query likeAny(T... ar) {
        return likeAny(Arrays.asList(ar));
    }

    public Query eqNonNull(String value) throws BadArgumentException {
        alert(value);
        return eq(value);
    }

    public <T> Query inNonEmpty(Collection<T> cl) throws BadArgumentException {
        alert(cl);
        return in(cl);
    }

    public <T> Query inNonEmpty(T... ar) throws BadArgumentException {
        alert(ar);
        return in(ar);
    }

    public <T> Query notInNonEmpty(Collection<T> cl) throws BadArgumentException {
        alert(cl);
        return notIn(cl);
    }

    public <T> Query notInNonEmpty(T... ar) throws BadArgumentException {
        alert(ar);
        return notIn(ar);
    }

    private boolean notEmpty() {
        return StringUtils.hasText(query);
    }

    private <T> boolean validate(T value) {
        if (value instanceof Collection) {
            return !CollectionUtils.isEmpty((Collection<?>) value);
        }
        if (value instanceof String) {
            return StringUtils.hasText((String) value);
        }
        return Objects.nonNull(value);
    }

    private void alert(String value) throws BadArgumentException {
        AlertUtils.isTrue(validate(value), () -> new BadArgumentException(alertTrue("value not empty")));
    }

    private void alert(Object value) throws BadArgumentException {
        AlertUtils.isTrue(validate(value), () -> new BadArgumentException(alertTrue("value not empty")));
    }

    private <T> void alert(Collection<T> value) throws BadArgumentException {
        AlertUtils.isTrue(validate(value), () -> new BadArgumentException(alertTrue("value not empty")));
    }

    private Query clearOp() {
        compositor = "";
        operand = null;
        return this;
    }

    private Query comp(@NonNull final String comp, @NonNull final String key) {
        AlertUtils.isTrue(StringUtils.hasText(key), alertTrue("Key not empty"));
        AlertUtils.isTrue(Objects.isNull(operand), alertTrue("Single key"));
        compositor = notEmpty() ? comp : "";
        operand = key;
        return this;
    }

    private Query comp(@NonNull final String comp, @NonNull final Query other) {
        if (!other.notEmpty()) {
            return this;
        }
        query = notEmpty() ? query.concat(comp).concat(other.query) : other.query;
        return this;
    }

    private <T> Query op(@NonNull String op, final T value, @NonNull Function<T, String> mapper) {
        AlertUtils.isTrue(Objects.nonNull(operand), alertTrue("Key not empty"));
        if (validate(value)) {
            query = query.concat(compositor).concat(operand).concat(op).concat(mapper.apply(value));
        }
        return clearOp();
    }
}
